package com.atguigu.yygh.hosp.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.yygh.hosp.repository.HospitalRepository;
import com.atguigu.yygh.hosp.repository.ScheduleRepository;
import com.atguigu.yygh.hosp.service.DepartmentService;
import com.atguigu.yygh.hosp.service.HospitalService;
import com.atguigu.yygh.hosp.service.ScheduleService;
import com.atguigu.yygh.model.hosp.BookingRule;
import com.atguigu.yygh.model.hosp.Department;
import com.atguigu.yygh.model.hosp.Hospital;
import com.atguigu.yygh.model.hosp.Schedule;
import com.atguigu.yygh.vo.hosp.BookingScheduleRuleVo;
import com.atguigu.yygh.vo.hosp.ScheduleOrderVo;
import com.atguigu.yygh.vo.hosp.ScheduleQueryVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 杨林
 * @create 2022-11-07 18:27 星期一
 * description:
 */
@Service
public class ScheduleServiceImpl implements ScheduleService {

    @Autowired
    private ScheduleRepository scheduleRepository;

    @Autowired
    private MongoTemplate mongoTemplate;  //实现聚合查询

    @Autowired
    private HospitalRepository hospitalRepository;


    /**
     * 上传排班信息
     */
    @Override
    public void save(Map<String, Object> paramMap) {
        //json转schedule对象
        String jsonString = JSONObject.toJSONString(paramMap);
        Schedule schedule = JSONObject.parseObject(jsonString, Schedule.class);

        //判断排班是否存在
        Schedule existSchedule =
                scheduleRepository.getScheduleByHoscodeAndHosScheduleId(schedule.getHoscode(), schedule.getHosScheduleId());

        //如果存在则修改  如果不存在则添加
        if (existSchedule == null){
            schedule.setCreateTime(new Date());
            schedule.setUpdateTime(new Date());
            schedule.setStatus(1);
        }else{
            schedule.setStatus(1);
            schedule.setUpdateTime(new Date());
            schedule.setCreateTime(existSchedule.getCreateTime());
            schedule.setId(existSchedule.getId());
        }

        scheduleRepository.save(schedule);
    }

    /**
     *
     * 排班分页查询
     */
    @Override
    public Page<Schedule> selectPage(Integer pageNum, Integer limit, ScheduleQueryVo scheduleQueryVo) {

        //排序
        Sort sort = Sort.by(Sort.Direction.DESC,"createTime");
        Pageable pageable = PageRequest.of(pageNum - 1, limit, sort);

        //查询条件
        Schedule schedule = new Schedule();
        BeanUtils.copyProperties(scheduleQueryVo, schedule);

        //模糊查询匹配器
        ExampleMatcher matcher = ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)  //模糊匹配
                                    .withIgnoreCase(true); //忽略大小写
        //example对象
        Example example = Example.of(schedule, matcher);

        //调用findAll()方法
        Page page = scheduleRepository.findAll(example, pageable);
        return page;
    }

    /**
     * 删除排班信息
     */
    @Override
    public void remove(String hoscode, String hosScheduleId) {
        Schedule schedule =
                scheduleRepository.getScheduleByHoscodeAndHosScheduleId(hoscode, hosScheduleId);

        if (schedule != null){
            scheduleRepository.deleteById(schedule.getId());
        }
    }

    /**
     * 根据医院编号和科室编号查询排班日期
     */

    @Autowired
    private HospitalService hospitalService;

    @Override
    public Map<String, Object> getScheduleRule(long page, long limit, String hoscode, String depcode) {

        //Map用于封装结果参数
        Map<String,Object> result = new HashMap<>();

        //根据hsocode和depcode查询排班
        Schedule scheduleList = scheduleRepository.getScheduleByHoscodeAndHosScheduleId(hoscode, depcode);

        //根据工作日期wokeDate进行分组
        Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode);
        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("workDate") //按照查询条件Criteria查询条件查询 然后将查询到的数据按照workDate分组
                        .first("workDate").as("workDate") //取出本组中的第一个workDate
                        //as括号中的字段对应实体类中的字段 （保持一致，否则无法赋值）
                        .count().as("docCount")  //统计每组的个数 别名cocCount
                        .sum("reservedNumber").as("reservedNumber")
                        .sum("availableNumber").as("availableNumber"),
                Aggregation.sort(Sort.Direction.ASC,"workDate"),
                Aggregation.skip((page - 1) * limit),
                Aggregation.limit(limit)
        );
        //调用方法 最终执行
        AggregationResults<BookingScheduleRuleVo> aggregate =
                mongoTemplate.aggregate(aggregation, Schedule.class, BookingScheduleRuleVo.class);


        //获取分组结果
        List<BookingScheduleRuleVo> bookingScheduleRuleVoList = aggregate.getMappedResults();

        //分组查询的总记录数
        Aggregation aggreTotal = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("workDate"));
        AggregationResults<BookingScheduleRuleVo> totalAggResults =
                mongoTemplate.aggregate(aggreTotal, Schedule.class, BookingScheduleRuleVo.class);
        int size = totalAggResults.getMappedResults().size();

        //星期 dayOfWeek
        for (BookingScheduleRuleVo bookingScheduleRuleVo : bookingScheduleRuleVoList) {
            String dayOfWeek = dayOfWeek(bookingScheduleRuleVo.getWorkDate());
            bookingScheduleRuleVo.setDayOfWeek(dayOfWeek);
        }

        //封装参数
        result.put("bookingScheduleRuleList", bookingScheduleRuleVoList);
        result.put("total", size);

        //根据hoscode获取hosname
        Hospital hospital = hospitalService.getByHoscode(hoscode);
        String hosname = hospital.getHosname();

        //封装参数
        Map<String,String> hosnameMap = new HashMap<>();
        hosnameMap.put("hosname", hosname);

        result.put("baseMap", hosnameMap);

        return result;
    }

    //根据医院编号 科室编号和工作日期查询排班的详细信息
    @Override
    public List<Schedule> getDetailSchedule(String hoscode, String depcode, String workDate) {
        //查询排班列表
        List<Schedule> scheduleList =
                scheduleRepository.findByHoscodeAndDepcodeAndWorkDate(hoscode,depcode,new DateTime(workDate).toDate());

        for (Schedule schedule : scheduleList) {
            packSchedule(schedule);
        }
        return scheduleList;
    }


    /**
     * 查询排班详情
     * @param page
     * @param limit
     * @param hoscode
     * @param depcode
     * @return
     */
    @Override
    public Map<String, Object> getBookingSchedule(Integer page, Integer limit, String hoscode, String depcode) {

        //显示的日期取决于预约周期 因此需要先拿到预约周期
        //获取预约周期
        Hospital hospital = hospitalRepository.findByHoscode(hoscode);
        BookingRule bookingRule = hospital.getBookingRule();

        IPage iPage = this.getListDate(bookingRule, page, limit);
        List<Date> pageDateList = iPage.getRecords();  //当前页的日期对象
        long pages = iPage.getPages();  //总页数
        long total = iPage.getTotal();  //总记录数

        //每个日期都对应一个BookScheduleRuleVo--->每个属性都要有值
        //workDate相同的一组排班 统计一些相同的数据封装到BookScheduleRuleVo中

        //针对指定医院 指定科室下的排班按照workDate进行分组
       Criteria criteria = Criteria.where("hoscode").is(hoscode)
               .and("depcode").is(depcode)
               .and("workDate").in(pageDateList);

        Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(criteria), //针对指定的日期进行分组排班统计
                Aggregation.group("workDate").count().as("docCount")//按照指定的字段进行分组
                .first("workDate").as("workDate")
                .sum("reservedNumber").as("reservedNumber") //这一组排班reservedNumber属性的总和
                .sum("availableNumber").as("availableNumber") //这一组排班availableNumber属性的总和
        );

        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(aggregation, Schedule.class, BookingScheduleRuleVo.class);
        List<BookingScheduleRuleVo> bookingScheduleRuleVoList = aggregate.getMappedResults();

        //将bookingScheduleRuleVoList转换成map map<workDate,bookingScheduleRuleVo>
        //根据workDate直接获取bookingScheduleRuleVo  这要比遍历bookingScheduleRuleVoList 性能高
        Map<Date, BookingScheduleRuleVo> map = bookingScheduleRuleVoList.stream().collect(Collectors.toMap(
                BookingScheduleRuleVo::getWorkDate, bookingScheduleRuleVo -> bookingScheduleRuleVo));

        List<BookingScheduleRuleVo> bookingScheduleRuleList = new ArrayList<>();

        //17---23号 每个日期都应该对应一个BookingScheduleRuleVo  还需要封装空的日期对象
//        for (Date date : pageDateList) {
        for (int i = 0;i < pageDateList.size();i++){
            Date date = pageDateList.get(i);
            //BookingScheduleRuleVo bookingScheduleRuleVo = this.getRuleVoByDate(bookingScheduleRuleVoList,date);
            BookingScheduleRuleVo bookingScheduleRuleVo = map.get(date);
            //如果bookingScheduleRuleVo为空 则说明该天没有排班 需要创建一个空的bookingScheduleRuleVo对象封装其他参数
            if (bookingScheduleRuleVo == null){
                bookingScheduleRuleVo = new BookingScheduleRuleVo();
                bookingScheduleRuleVo.setReservedNumber(-1);  //没有排班
                bookingScheduleRuleVo.setAvailableNumber(-1);  //没有排班
                bookingScheduleRuleVo.setWorkDate(date);  //排班日期
                bookingScheduleRuleVo.setDocCount(0);  //排班的数量
            }

            //每个bookScheduleRuleVo都需要给额外的字段赋值
            bookingScheduleRuleVo.setWorkDateMd(date);  //另一种日期格式
            bookingScheduleRuleVo.setDayOfWeek(this.dayOfWeek(date));  //周几

            //状态 0：正常 1：即将放号 -1：当天已停止挂号
            //最后一页的最后一条显示即将放号
            if (page == pages && i == pageDateList.size()-1){  //表示当前页是最后一页的最后一条数据
                bookingScheduleRuleVo.setStatus(1); //表示即将放号
            }else {
                bookingScheduleRuleVo.setStatus(0);  //表示正常
            }

            //停挂时间
            //判断当天的停挂时间（即第一页的第一条数据）
            if (page == 1 && i == 0){
                String stopTime = bookingRule.getStopTime();  //11:30
                //获取当前医院停挂时间
                DateTime dateTime = this.getDateTime(new Date(), stopTime);
                if (dateTime.isBeforeNow()){  //表示dateTime在当前时间之前  说明已经超过停挂时间
                    bookingScheduleRuleVo.setStatus(-1);
                }
            }
            bookingScheduleRuleList.add(bookingScheduleRuleVo);
        }
        Map<String,Object> result = new HashMap<>();
        result.put("bookingScheduleList", bookingScheduleRuleList);
        result.put("total", total);  //总日期个数

        Map<String,Object> baseMap = new HashMap<>();

        //封装页面上的其他数据
        result.put("baseMap", baseMap);
        baseMap.put("hosname", hospital.getHosname());
        Department department = departmentService.getDepartment(hoscode, depcode);
        baseMap.put("bigname",department.getBigname()); //大科室名称
        baseMap.put("depname", department.getDepname());  //小科室名称
        baseMap.put("workDateString", new DateTime().toString("yyyy年MM月"));
        baseMap.put("releaseTime", bookingRule.getReleaseTime());
        baseMap.put("stopTime", bookingRule.getStopTime());

        return result;
    }


    /**
     * 根据id获取排班
     * @param id
     * @return
     */
    @Override
    public Schedule getById(String id) {
        Schedule schedule = scheduleRepository.findById(id).get();
        this.packSchedule(schedule);
        return schedule;
    }

    //根据排班id获取预约下单数据 （医院 + 排班数据）
    //调用医院端接口 需要的数据从这个vo中获取
    //创建平台订单是需要的参数也从这个vo中获取
    @Override
    public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {

        ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();

        //1.查询排班
        Schedule schedule = scheduleRepository.findById(scheduleId).get();

        //2.查询预约规则
        Hospital hospital = hospitalService.getByHoscode(schedule.getHoscode());
        BookingRule bookingRule = hospital.getBookingRule();

        //3.赋值
        scheduleOrderVo.setDepcode(schedule.getDepcode());
        scheduleOrderVo.setHoscode(schedule.getHoscode());
        scheduleOrderVo.setHosname(hospital.getHosname());
        Department department = departmentService.getDepartment(schedule.getHoscode(), schedule.getDepcode());
        scheduleOrderVo.setDepname(department.getDepname());
        scheduleOrderVo.setHosScheduleId(schedule.getHosScheduleId());
        scheduleOrderVo.setAvailableNumber(schedule.getAvailableNumber());
        scheduleOrderVo.setTitle(schedule.getTitle());
        scheduleOrderVo.setReserveDate(schedule.getWorkDate());
        scheduleOrderVo.setReserveTime(schedule.getWorkTime());  //0:上午  1：下午
        scheduleOrderVo.setAmount(schedule.getAmount());

        //退号截至天数（就诊前一天为：0 当天为：-1）
        Integer quitDay = bookingRule.getQuitDay();  //截至天数
        String quitTime = bookingRule.getQuitTime();
        String s = new DateTime(schedule.getWorkDate()).plusDays(quitDay).toString("yyyy-MM-dd") + " " + quitTime;
        //退票的具体时间
        Date date_quiteTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(s).toDate();
        scheduleOrderVo.setQuitTime(date_quiteTime);

        String releaseTime = bookingRule.getReleaseTime();
        Date startDate = getDateTime(new Date(), releaseTime).toDate();
        scheduleOrderVo.setStartTime(startDate);  //预约开始时间  （当天）

        Integer cycle = bookingRule.getCycle();
        String stopTime = bookingRule.getStopTime();
        Date stopDate = getDateTime(new DateTime().plusDays(cycle).toDate(), stopTime).toDate();
        scheduleOrderVo.setEndTime(stopDate);   //预约结束时间
        scheduleOrderVo.setStopTime(this.getDateTime(new Date(), stopTime).toDate());  //当天挂号的停止时间
        return scheduleOrderVo;
    }

    /**
     * 跟新排班 （排班中的号源数量）
     * @param schedule
     */
    @Override
    public void update(Schedule schedule) {
        schedule.setUpdateTime(new Date());
        scheduleRepository.save(schedule);
    }

//    private BookingScheduleRuleVo getRuleVoByDate(List<BookingScheduleRuleVo> bookingScheduleRuleVoList,Date date) {
//        for (BookingScheduleRuleVo bookingScheduleRuleVo : bookingScheduleRuleVoList) {
//            if (bookingScheduleRuleVo.getWorkDate().equals(date)){
//                return bookingScheduleRuleVo;
//            }
//
//        }
//        return null;
//    }


    private IPage getListDate(BookingRule bookingRule,Integer page, Integer limit){
        Integer cycle = bookingRule.getCycle(); //预约周期

        String releaseTime = bookingRule.getReleaseTime();  //08:30（字符串）
        DateTime dateTime = this.getDateTime(new Date(), releaseTime);

        if (dateTime.isBeforeNow()){
            //说明当天已经放号
            cycle += 1;
        }
        //按照预约周期创建cycle个日期对象（总的日期个数）
        List<Date> dateList = new ArrayList<>();
        //根据cycle创建日期对象
        //遍历cycle 创建date对象
        for (Integer integer = 0; integer < cycle; integer++) {
            //创建cycle个连续的日期
            Date date = new DateTime().plusDays(integer).toDate();
            //去掉日期带有的时分秒
            String s = new DateTime(date).toString("yyyy-MM-dd");
            date = DateTimeFormat.forPattern("yyyy-MM-dd").parseDateTime(s).toDate();
            dateList.add(date);
        }

        //分页
        //page = 1 limit = 7
        int begin = (page - 1) * limit;
        int end = (page - 1) * limit + limit;

        //最后一页不够7条的情况
        if (end > dateList.size()){
            end = dateList.size();
        }

        List<Date> pageDateList = new ArrayList<>();
        for (int i = begin; i < end; i++) {
            Date date = dateList.get(i);
            pageDateList.add(date);
        }
        //总记录数  总页数  (方式1)
//        int size = dateList.size();
//        int pages = size / limit + size % limit == 0 ? 0 : 1;

        //总记录数  总页数  (方式2)
        com.baomidou.mybatisplus.extension.plugins.pagination.Page datePage
                = new com.baomidou.mybatisplus.extension.plugins.pagination.Page(page, limit, dateList.size());
        datePage.setRecords(pageDateList);  //当前页数

//        long total = datePage.getTotal(); //总记录数
//        long pages = datePage.getPages();  //总页数
//        List records = datePage.getRecords();  //分页数据
        return datePage;
    }


    private DateTime getDateTime(Date date,String time){
        //当前医院的放号时间
        //String releaseTime = bookingRule.getReleaseTime();  //08:30（字符串）
        String s = new DateTime(date).toString("yyyy-MM-dd");  //2022-11-16
        //放号时间的完整格式
        String string = s + " " + time;
        DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(string);
        return dateTime;
    }


    private String dayOfWeek(Date date){
        DateTime dateTime = new DateTime(date);
        int dayOfWeek = dateTime.getDayOfWeek();  //返回的是int
        String[] arr = {"星期一","星期二","星期三","星期四","星期五","星期六","星期天"};
        String weekDay = arr[dayOfWeek - 1];
        return weekDay;
    }

    @Autowired
    private DepartmentService departmentService;

    private void packSchedule(Schedule schedule){
        Hospital hospital = hospitalService.getByHoscode(schedule.getHoscode());
        //医院名称
        schedule.getParam().put("hosname", hospital.getHosname());
        //科室名称
        schedule.getParam().put("depname", departmentService.getDepName(schedule.getHoscode(),schedule.getDepcode()));
        //日期对应星期
        schedule.getParam().put("dayOfWeek", this.dayOfWeek(schedule.getWorkDate()));
    }
}
